<template>
    <div style="border: 1px solid #ccc">
        <slot name="head"></slot>
        <my-search-bar :tbname="tbname" v-model="search" ref="search_bar" v-if="save2Server && showSearch"
            :excludeSearch="excludeSearch" style="padding: 6px;border-bottom: 1px dashed #ccc;width: 100%;" />
        <template v-if="grid">
            <table class="table pure-table" style="width:100%;">
                <tr>
                    <th v-if="allowSelect2"><el-checkbox @change="checkPage"></el-checkbox></th>
                    <th v-for="(col, ix) in fields" :key="col" :style="{ width: this.widths[ix] }">{{ colLabels[ix] }}
                    </th>
                    <th v-if="actions"></th>
                </tr>
                <tr v-for="(it, rowix) in page.records" :key="it">
                    <td v-if="allowSelect2"><el-checkbox v-model="it._check"></el-checkbox></td>
                    <template v-for="(col, ix) in fields" :key="col">
                        <td v-if="rowspans[rowix][col]" :rowspan="rowspans[rowix][col]" :style="{ textAlign: cols[col].align }">
                            <el-link @click="doEdit(it)" v-if="ix == 0" type="primary">{{ getFieldValue(it, col)}}</el-link>

                            <component v-else-if="cols[col].tpl" 
                                :is="cols[col].tpl.control" 
                                :modelValue="it[col]"
                                @update:modelValue="newVal => doUpdateItem(it, col, newVal)" 
                                :disabled="disabled"
                                @blur="onBlur" @change="onBlur" 
                                :options="cols[col].options" 
                                v-bind="buildProps(cols[col].tpl.props, it)" 
                                :formData="it" />

                            <my-img v-else-if="isPhotoCol(col)" fit="contain" :src="getFieldValue(it, col)"
                                style="width:48px;height:48px;" />

                            <span v-else>{{ getFieldValue(it, col) }}</span>
                        </td>
                    </template>
                    <td v-if="actions">
                        <el-space spacer="|">
                            <template v-for="btn in actions" :key="btn.label">
                                <el-link :type="btn.type" v-if="btn.label && checkIf(btn.vif, it)"
                                    @click="doClick(btn.click, it)">{{ btn.label }}</el-link>
                            </template>
                        </el-space>
                    </td>
                </tr>
            </table>
            <el-pagination :layout="$mbh5 ? pageLayoutH5 : pageLayout" background v-model:current-page="page.pageIndex"
                :page-size="page.pageSize" :total="page.totalCount" :pager-count="$mbh5?5:7" 
                style="float: right; margin:6px 0;"></el-pagination>

        </template>
        <draggable v-else-if="allowDrag" v-model="items" :disabled="disabled" item-key="@">
            <template #item="{ element: item }">
                <component :is="cell2.cellType" :formData="item" :key="item" :tbname="tbname" v-bind="cell2"
                    @click="doEdit(item)" :isLink="false" :isCheck="allowSelect2"
                    @update:formData="updateCellData(item, $event)" :disabled="disabled" @delete="deleteCell(item)"
                    v-model:check="item._check" @reload="reloadPage">
                </component>
            </template>
        </draggable>

        <my-list-box v-else v-model="items" :childAttrs="cell2" :showEmpty="showEmpty" :pageSize="pageSize">
            <template #="{ item }">
                <component :is="cell2.cellType" :formData="item" :key="item" :tbname="tbname" v-bind="cell2"
                    @click="doEdit(item)" :isLink="false" :isCheck="allowSelect2"
                    @update:formData="updateCellData(item, $event)" :disabled="disabled"
                    @delete="deleteCell(item, $event)" v-model:check="item._check">
                </component>
            </template>
        </my-list-box>

        <div style="clear:both;padding: 6px; border-top:1px dashed #ccc;">
            <el-space>
                <el-checkbox v-if="allowRemove2 || (!disabled && batchSetFields)" @change="checkAll"></el-checkbox>
                <el-button v-if="allowAdd2" @click="doAdd">{{ getLabel("新增") }}</el-button>
                <el-button v-if="allowRemove2" @click="doRemove">{{ getLabel("移除") }}</el-button>
                <template v-for="ac in moreActions2" :key="ac.label">
                    <el-dropdown v-if="ac.label && checkIf(ac.vif) && ac.options" split-button :type="ac.type"
                        @command="doClick(ac.click, $event)" @click="doClick(ac.click, $event)">
                        {{ ac.label }}
                        <template #dropdown>
                            <el-dropdown-menu>
                                <template v-for="menu in ac.options" :key="menu">
                                    <el-dropdown-item v-if="menu.label != '-'" :command="menu.value">{{ menu.label
                                        }}</el-dropdown-item>
                                    <el-dropdown-item v-else divided></el-dropdown-item>
                                </template>
                            </el-dropdown-menu>
                        </template>
                    </el-dropdown>
                    <el-button v-else-if="ac.label && checkIf(ac.vif)" @click="doClick(ac.click)" :type="ac.type">{{
                        ac.label
                        }}</el-button>
                </template>
            </el-space>
        </div>

        <el-form v-if="!disabled && batchSetFields" ref="formBatchSet" :label-width="120" :model="batchFormData"
            :rules="batchRules" label-position="right" style="padding:0 8px 8px 8px;">
            <hr />
            <my-form-row v-if="tb" :tb="tb" :row="batchSetFields" :formData="batchFormData"></my-form-row>
            <hr />
            <el-button type="primary" @click="doBatchSet">批量设置</el-button>
        </el-form>

        <slot name="footer"></slot>
    </div>
</template>

<script>
import MyListBox from "./MyListBox.vue";
import { Utils } from "./main";
import { reactive, defineComponent } from 'vue'

export default defineComponent({
    components: { MyListBox },
    name: "SubListForm",
    props: {
        tbname: {
            type: String,
            required: true,
        },
        formName: String,
        modelValue: Array,
        disabled: Boolean,
        allowAdd: {
            type: [Boolean, Function],
            default: true
        },
        allowRemove: {
            type: [Boolean, Function],
            default: true
        },
        allowEdit: {
            type: Boolean,
            default: true
        },
        showEmpty: {
            type: Boolean,
            default: true
        },
        allowDrag: {
            type: Boolean,
            default: false
        },
        pageSize: {
            type: Number,
            default: 6
        },
        pageLayout: {
            type: String,
            default: 'total, prev, pager, next,jumper',
        },
        pageLayoutH5: {
            type: String,
            default: 'prev, pager, next',
        },
        showSearch: Boolean,//是否显示检索栏
        excludeSearch: String,//额外排除的搜索字段分号(;)分隔
        AddClick: Function,
        RemoveClick: Function,
        EditClick: Function,
        formData: Object,
        grid: String,
        gridFields: String,
        gridMerge: String,//合并行，多个字段名，按第一个字段的内容进行合并，后面的字段和他的合并数一致
        actions: Array,//grid操作按钮
        save2Server: Boolean,//直接保存到服务器
        pidField: String,
        pidSearchField: String,//子表查询数据是否需要用到的字段
        cell: Object,
        pidFieldCode: {
            //父表的ID字段
            type: String,
            default: "ID",
        },
        sortField: String,
        sortFunc: Function,
        editDlgProps: Object,//新增或编辑的时候传入给对话框的额外参数
        allowSelectMulti: [Boolean, Function],//是否允许多选
        moreActions: Array,//更多的操作按钮
        labels: Object,
        batchSetFields: String,
        filter: Function,//用于过滤出显示用的数据，有时候不需要显示全部，或者需要按条件进行显示
        rowCalc: Function,//行数据修改以后执行自定义关联更新
    },
    provide() {
        return {
            SubListForm: this,
        };
    },
    inject: {
        MyPage: { default: null },
        MyListPage: { default: null },
        CheckAll: { default: null },
    },
    emits: ["update:modelValue", "mounted"],
    data() {
        return {
            tb: null,
            batchRules: {},
            batchFormData: {},//批量设置数据

            isItemsSetting: false,
            // fields: [],
            widths: [],
            // cols: [],
            cell2: { title: "", value: "", label: "", icon: "", cellType: "my-cell" },
            hasChanged: false,
            page: {
                pageIndex: 1,
                pageSize: this.pageSize,
                totalCount: 0,
                pageCount: 1,
                records: []
            },
            search: {}
        }
    },
    created() {

    },
    mounted() {
        //这里原来放在created事件中， 由于采用了异步操作，造成mounted事件有时候会被漏掉，改为beforeMount事件以后，就OK了
        $server.getDM(this.tbname).then(tb => {
            this.tb = tb;
            let cell = this.cell || { ...tb.cell };
            this.cell2 = { ...this.cell2, ...cell };
            this.buildFields();

            let rules = {};
            if (this.batchSetFields) {
                let flds = this.batchSetFields.split(/,|;/g);
                flds.forEach(fldCode => {
                    let col = tb.columns.find(x => x.code == fldCode);
                    if (col && col.required) {
                        let tp = undefined;
                        switch (col.type.toLowerCase()) {
                            case "string":
                            case "text":
                                tp = "string";
                                break;
                            case "bool":
                            case "boolean":
                                tp = "boolean";
                                break;
                            case "int":
                                tp = "integer"
                                break;
                            case "array":
                                tp = "array";
                                break;
                            case "date":
                            case "datetime":
                            case "time":
                                tp = "date";
                                break;
                            case "object":
                                tp = "object";
                                break;
                            case "number":
                            case "number2":
                            case "number4":
                                tp = "number";
                                break;
                        };
                        rules[col.code] = [{ type: tp, required: true, trigger: 'blur', message: `请录入${col.label}` }];
                    }
                })
            }
            this.batchRules = rules;
        }).then(res1 => {
            if (this.CheckAll) {
                this.CheckAll.addItem(this);
            }
            this.makePage();
            this.loadServerData();
            $server.delay(500).then(() => {
                this.$emit("mounted", this);
            })
        })
    },
    unmounted() {
        if (this.CheckAll) {
            this.CheckAll.removeItem(this);
        }
    },
    watch: {
        modelValue(newVal) {
            this.makePage();
        },
        'page.pageIndex'() {
            this.makePage();
        },
        'page.pageSize'() {
            this.makePage();
        },
        search(newVal) {
            this.loadServerData();
        },
        'formData.ID'() {
            this.loadServerData();
        },
        formData() {
            this.loadServerData();
        },
        grid() {
            this.buildFields();
        }
    },
    methods: {
        isPhotoCol(fieldCode) {
            var col = this.tb.columns.find(x => x.code == fieldCode);
            if (!col) return false;
            return col.control == 'photo' || col.control == 'HandWriteBoard' || col.control == 'sign' || col.control == 'MultiSign';
        },
        buildFields() {
            // let gg = this.gridFields || (this.tb.grid.fields != undefined ? this.tb.grid.fields : this.tb.grid);
            // this.fields = gg.split(",").map((p, rowix) => {
            //     let ix = p.indexOf(":");
            //     if (ix == -1) {
            //         return p;
            //     }
            //     this.widths[rowix] = p.substr(ix + 1) + 'px';
            //     return p.substr(0, ix);
            // });
            // this.cols = Object.fromEntries(this.fields.map(p => {
            //     let c = this.tb.columns.find(p1 => p1.code == p);
            //     return [c.code, c];
            // }));
        },
        getLabel(label) {
            let labels = this.labels;
            if (labels) {
                return labels[label] || label;
            }
            return label;
        },
        loadServerData() {
            if (!this.save2Server || !this.formData.ID) return;
            let pm = { ...this.search };
            pm[this.pidSearchField || this.pidField] = this.formData.ID;
            $server.list(this.tbname, 1, 1000, pm, this.$el).then(res => {
                this.items = res.records;
            });
        },
        makePage(pageIndex) {
            pageIndex = pageIndex || this.page.pageIndex;
            let pszie = this.pageSize || 10;
            let ptotal = this.items.length;
            let ppage = Math.ceil(ptotal / pszie);
            let ix = (pageIndex - 1) * pszie;
            this.page = {
                pageIndex: pageIndex,
                pageSize: pszie,
                totalCount: ptotal,
                pageCount: ppage,
                records: this.items.slice(ix, ix + pszie)
            }
        },
        doCheckAll(checked) {
            //上级容器可能发送来全选操作
            if (!this.disabled) {
                this.checkAll(checked);
            }
        },
        reloadPage() {
            //重新加载所在页面
            //form, list
            if (this.MyPage) {
                this.MyPage.loadData();
                return;
            }
            if (this.MyListPage) {
                this.MyListPage.loadData();
            }
        },
        checkAll(val) {
            this.items = this.items.map(p => {
                p._check = val;
                return p;
            });
        },
        checkPage(val) {
            this.page.records.forEach(p => {  //) = this.page.records(p => {
                p._check = val;
                return p;
            });
        },
        async doAdd() {
            if (this.disabled) return;
            if (this.AddClick) {
                this.AddClick.apply(this);
                return;
            }
            let item = await Utils.getFormDefault(this.tbname, this.search);
            item._pformData = this.formData;
            if (this.pidField) {
                let srcFlds = this.pidFieldCode.replaceAll(",", ";").split(";");
                let destFlds = this.pidField.replaceAll(",", ";").split(";");
                for (let ix = 0; ix < srcFlds.length; ix++) {
                    item[destFlds[ix]] = this.formData[srcFlds[ix]];
                }
            }
            let pops = { ...this.editDlgProps, pForm: this.MyPage, formName: this.formName };
            $server.dlg.create(this.tbname, this.save2Server ? 0 : item, item, pops).then(res => {
                delete res._pformData;
                try {
                    const key = this.sortField || "__index";
                    let max = this.items.length + 1;
                    this.items.forEach(p1 => {
                        if (p1[key] > max) {
                            max = p1[key] + 1;
                        }
                    })
                    res[key] = max + 1;
                } catch { }

                this.items = this.items.concat(res);
            })
        },
        doEdit(item) {
            if (this.EditClick) {
                this.EditClick.apply(this, [item]);
                return;
            }
            this.doDefaultEdit(item);
        },
        doDefaultEdit(item) {
            let action = $server.dlg.edit;
            if (this.disabled || this.allowEdit == false) {
                action = $server.dlg.view;
            };

            var ix = this.items.indexOf(item);
            item._pformData = this.formData;
            let pops = { ...this.editDlgProps, pForm: this.MyPage };
            action(this.tbname, (this.save2Server ? item.ID : item), (this.save2Server ? undefined : item), this.formName, pops).then(res => {
                delete res._pformData;
                if (res._action == "delete") {
                    this.items = this.items.slice(0, ix).concat(this.items.slice(ix + 1));
                    return;
                }
                if (res._action == "reload") {
                    this.reloadPage();
                    return;
                }
                let item = { ...res };
                this.items.splice(ix, 1, item);
                this.items = this.items.map(p => p);
            })
        },
        doRemove() {
            if (this.disabled) return;
            if (this.RemoveClick) {
                this.RemoveClick.apply(this);
                return;
            }
            let delItems = this.items.filter(p => p._check);
            if (delItems && delItems.length > 0) {
                if (this.save2Server) {
                    $msg.confirm(this.getLabel("确定删除吗?")).then(() => {
                        delItems.forEach(row => {
                            $server.call(this.tb.call.delete, { id: row.ID }).then(res => {
                                this.items = this.items.filter(p => p.ID != row.ID);
                            });
                        })
                    })
                } else {
                    this.items = this.items.filter(p => !p._check);
                }
            }
        },
        updateCellData(item, res) {
            //如果单元格里面改变了绑定的item, 需要做相应的更新，让改变反应到list上来
            //例如：单元格里面做了 提交，等等的操作
            let ix = this.items.indexOf(item);
            this.items = this.items.slice(0, ix).concat(res, this.items.slice(ix + 1));
        },
        deleteCell(item) {
            //单元格里面发出的删除操作
            let ix = this.items.indexOf(item);
            this.items = this.items.slice(0, ix).concat(this.items.slice(ix + 1));
        },
        checkIf(vif, row) {
            if (typeof vif == "function") {
                return vif.apply(this, [row]);
            }
            return true;
        },
        doClick(func, row) {
            Utils.doFunc(func, this, [row]);
        },

        doUpdateItem(it, col, newVal) {
            it[col] = newVal;
            if (this.rowCalc) {
                this.rowCalc.apply(this, [it, col, newVal])
            }
            this.hasChanged = true;
        },
        onBlur() {
            if (!this.disabled && this.hasChanged) {
                this.items = this.items;
                this.hasChanged = false;
            }
        },
        getFieldValue(row, tag) {
            let val = this.$server.dm_format(this.tb.columns, row, tag);
            switch (typeof (val)) {
                case "boolean":
                    return val ? "√" : "";
            }
            return val;
        },
        doBatchSet() {
            if (this.disabled) return;
            const its = this.items.filter(x => x._check == true);
            if (its.length == 0) {
                $msg.alert("请选中需要设置的记录");
                return;
            }

            this.$refs.formBatchSet.validate((isOk, errs) => {
                if (!isOk) {
                    var msgs = [];
                    Object.keys(errs).forEach(key => {
                        var items = errs[key];
                        if (items.forEach) {
                            items.forEach(item => {
                                msgs.push(item.message);
                            })
                        }
                    })
                    $msg.error(msgs.join("<br />"));
                } else {
                    const flds = this.batchSetFields.split(/,|;/g).filter(x => x.length > 0).map(x => x.trim());
                    this.items = this.items.map(it => {
                        if (it._check) {
                            flds.forEach(fld => {
                                it[fld] = this.batchFormData[fld];
                            })
                        }
                        return it;
                    });
                }
            });
        },
        buildProps(pp, obj){
            if(!pp || typeof(pp)!="object")return pp;
            var newpp = {...pp};
            Object.keys(pp).forEach(k=>{
                if(typeof(pp[k])=="string" && pp[k].startsWith("@")){
                    try{
                        let str=pp[k].substring(1);
                        newpp[k]=obj[str];
                    }catch{}
                }
            }) 
            return newpp;
        }
    },
    computed: {
        fields(){
            let gg = this.gridFields || (this.tb.grid.fields != undefined ? this.tb.grid.fields : this.tb.grid);
            const ff = gg.split(",").filter(x=>!!x).map(x=>x.trim()).filter(x=>x.length>0).map((p, rowix) => {
                let ix = p.indexOf(":");
                if (ix == -1) {
                    return p;
                }
                this.widths[rowix] = p.substr(ix + 1) + 'px';
                return p.substr(0, ix);
            });
            return ff;
        },
        cols(){
            const cc = Object.fromEntries(this.fields.map(p => {
                let c = this.tb.columns.find(p1 => p1.code == p);
                return [c.code, c];
            }));
            return cc;
        },
        rowspans() {
            //当前页的列的合并行数
            var ix = (this.page.pageIndex - 1) * this.page.pageSize;
            let spans = this.allrowspans.slice(ix, ix + this.page.pageSize);
            return spans;
        },
        allrowspans() {
            //计算所有列和合并行数
            let flds = (this.gridMerge || "").split(/[,;，；]/g).filter(x => x.length > 0);

            let spans = []
            if (flds.length == 0) {
                this.items.forEach((row, ix) => {
                    let srow = {}
                    this.fields.forEach(x => {
                        srow[x] = 1;
                    });
                    spans.push(srow);
                });
                return spans;
            }
            if (flds.length == 0) {
                this.items.forEach((row, ix) => {
                    let srow = {}
                    this.fields.forEach(x => {
                        srow[x] = 1;
                    });
                    spans.push(srow);
                });
                return spans;
            }
            if (flds.length > 0) {
                const key = flds[0];
                let lastVal = null;
                let lastIx = 0;
                let rowspan = 0;
                this.items.forEach((row, ix) => {
                    //需要考虑翻页，当前页第一行
                    const isPageStart = (ix % this.page.pageSize) == 0;
                    let srow = {}
                    this.fields.forEach(x => {
                        srow[x] = flds.includes(x) ? 0 : 1;
                    });
                    let val1 = row[key] || "";
                    if (val1 != lastVal || isPageStart) {
                        if (lastVal) {
                            spans[lastIx][key] = rowspan;
                            for (var ix3 = 1; ix3 < flds.length; ix3++) {
                                spans[lastIx][flds[ix3]] = rowspan;
                            }
                        }
                        lastIx = ix;
                        lastVal = val1;
                        rowspan = 1;
                    } else {
                        rowspan++;
                    }

                    spans.push(srow);
                })
                if (this.items.length > 0) {
                    for (var ix3 = 0; ix3 < flds.length; ix3++) {
                        spans[lastIx][flds[ix3]] = rowspan;
                    }
                }
            }

            return spans;
        },
        colLabels() {
            let str = (this.grid || "").replaceAll(";", ",");
            return str.split(",");
        },
        allowAdd2() {
            if (this.disabled) return false;

            if (this.allowAdd == false || this.allowAdd == true) return this.allowAdd;
            if (typeof (this.allowAdd) == "function") {
                return this.allowAdd.apply(this);
            }
            return true;
        },
        allowRemove2() {
            if (this.disabled) return false;

            if (this.allowRemove == false || this.allowRemove == true) return this.allowRemove;
            if (typeof (this.allowRemove) == "function") {
                return this.allowRemove.apply(this);
            }
            return true;
        },
        allowSelectMulti2() {
            if (this.allowSelectMulti == false || this.allowSelectMulti == true) return this.allowSelectMulti;
            if (typeof (this.allowSelectMulti) == "function") {
                return this.allowSelectMulti.apply(this);
            }
            return false;
        },
        allowSelect2() {
            return !this.disabled && (this.allowRemove2 || this.allowSelectMulti2 || this.moreActions2.length > 0 || !!this.batchSetFields);
        },
        moreActions2() {
            let ops = (this.moreActions || []).filter(p => p.label && (!p.vif || this.checkIf(p.vif))).map(x => {
                if (x.options && typeof (x.options) == "string") {
                    x.options = x.options.split(";").map(x1 => ({ value: x1, label: x1 }));
                }
                return x;
            });
            return ops;
        },
        items: {
            get() {
                if (!this.modelValue) return [];
                let its = this.modelValue.filter(x => this.filter ? this.filter(x) : true).map(p => {
                    let item = { ...p, _pformData: this.formData };
                    return item;
                });
                if (this.sortFunc) {
                    its.sort(this.sortFunc);
                } else if (this.sortField) {
                    try {
                        its.sort((a, b) => a[this.sortField] - b[this.sortField]);
                    } catch { }
                }
                return reactive(its);
            },
            set(items2) {
                items2.forEach((p, index) => {
                    if (!p["ID"]) {
                        p["ID"] = 0;
                    }
                })
                let items3 = items2.map(p => {
                    let item2 = { ...p };
                    delete item2["_pformData"];
                    delete item2["_action"];
                    return item2;
                })
                if (this.filter) {
                    let others = (this.modelValue || []).filter(x => !this.filter(x));
                    if (others.length > 0) {
                        items3 = items3.concat(others)
                    }
                }
                if (!this.grid && this.allowDrag) {
                    //拖动以后更新排序顺序
                    if(this.sortField){
                        items3.forEach((x,ix)=>x[this.sortField]=ix);
                    }
                }
                this.$emit("update:modelValue", items3);
            },
        }
    },

})
</script>
